<script setup lang="ts" generic="T extends ViewCard">
import { minBy } from 'lodash-es'
import { vElementSize } from '@vueuse/components'
import MyColumn from './Column.vue'
import { loadImage } from './utils'
import { useCalculateCols } from './hooks'
import type { ViewCard, MyWaterfallProps, ColumnsData } from './type'

type ColumnsDataType = ColumnsData<T>

const props = withDefaults(defineProps<MyWaterfallProps<T>>(), {
  showDominantColor: true,
  keyField: 'id',
  list: () => [],
  waterFallParams: () => ({
    defaultItemHeight: 180, // 默认高度
    align: 'center',
    // 卡片之间的间隙
    gutter: 12,
    buffer: 5, // 每列上下预渲染多少条数据
    // 是否有周围的gutter
    hasAroundGutter: false,
    // 卡片在PC上的宽度
    width: 290,
    // 自定义行显示个数，主要用于对移动端的适配
    breakpoints: {
      1200: {
        // 当屏幕宽度小于等于1200
        rowPerView: 4
      },
      800: {
        // 当屏幕宽度小于等于800
        rowPerView: 3
      },
      500: {
        // 当屏幕宽度小于等于500
        rowPerView: 2
      }
    }
  }),
  leftTopParams: () => ({
    widthHeightRatio: 0,
    columnNum: 0
  })
})

const scrollContainer = ref<HTMLElement | null>(null)

const containerRef = useTemplateRef('containerRef')

// const containerBounding = useElementBounding(containerRef)

watch(
  () => props.scrollContainerId,
  () => {
    if (!import.meta.client) return
    if (typeof props.scrollContainerId === 'string') {
      // console.log('scrollContainer1', document.getElementById(props.scrollContainerId))
      scrollContainer.value = document.getElementById(props.scrollContainerId)
    } else if (props.scrollContainerId instanceof HTMLElement) {
      // console.log('scrollContainer2', props.scrollContainerId)
      scrollContainer.value = props.scrollContainerId
    }
  },
  { immediate: true }
)

const scrollContainerOption = useScroll(scrollContainer, {
  throttle: 16 // 约60fps
  // behavior: 'smooth'
  // onScroll: (e: Event) => {
  //   console.log('useScroll', e)
  // },
  // onError: error => {
  //   console.log('useScroll', error)
  // }
})

const colWidth = ref(props.waterFallParams.width)

const emptySpaceRef = useTemplateRef('emptySpaceRef')

const emptySapceWidth = computed(() => {
  const columnNum = props.leftTopParams.columnNum
  // if (colWidth.value <= 2) {
  //   return props.waterFallParams.width * props.leftTopParams.columnNum
  // }
  return colWidth.value * columnNum + props.waterFallParams.gutter * (columnNum - 1)
})

// const carouselHeight = computed(() => props.leftTopParams.widthHeightRatio * emptySapceWidth.value)

const carouselHeight = ref(0)

const carouselHeightObserver = useResizeObserver(emptySpaceRef, entries => {
  if (!props.leftTopParams.widthHeightRatio) {
    const entry = entries[0]
    const { height } = entry.contentRect
    carouselHeight.value = height
    // console.log(66666666666, entry.contentRect)
  }
})

watch(
  () => props.leftTopParams.widthHeightRatio,
  val => {
    if (val) {
      carouselHeightObserver.stop()
      carouselHeight.value = val * emptySapceWidth.value
    }
  }
)

const emptySapceStyle = computed(() => ({
  width: `${emptySapceWidth.value}px`
}))

const columnOnResize = ({ width, height: _ }: { width: number; height: number }) => {
  // console.log('columnOnResize', width)
  colWidth.value = width
}

const { waterfallWrapper, cols } = useCalculateCols(props.waterFallParams)

const columnData = shallowRef<
  {
    columnId: number
    totalHeight: number
    items: T[]
  }[]
>(
  Array.from<ColumnsDataType, ColumnsDataType>({ length: cols.value }, (_, index) => ({
    columnId: index,
    items: [],
    totalHeight: index < props.leftTopParams.columnNum ? carouselHeight.value : 0
  }))
)

// const allItems = toRef(props, 'list')

const allItems = shallowRef(props.list)

watch(
  () => props.list,
  () => {
    allItems.value = props.list
  }
)

const setItemSize = ({ item, image }: { item: ViewCard; image?: HTMLImageElement }) => {
  const defaultItemHeight = props.waterFallParams.defaultItemHeight
  if (image) {
    item.width = image.width
    item.height = image.height
    const displayHeight =
      (colWidth.value * (item.height || defaultItemHeight)) / (item.width || defaultItemHeight)
    item.displayHeight = displayHeight
  } else {
    item.error = true
    item.displayHeight = defaultItemHeight
  }
  item.finish = true
}

const getAllItemsImgSize = () => {
  const items = allItems.value.filter(item => !item.width || !item.height)
  for (let index = 0; index < items.length; index++) {
    const item = items[index]
    loadImage(item.src)
      .then(image => {
        setItemSize({ item, image })
      })
      .catch(() => setItemSize({ item }))
  }
}

// watchEffect(() => {
//   getAllItemsImgSize()
// })
watch(allItems, () => {
  getAllItemsImgSize()
})

const debounceTime = ref(300)

const getColumnsData = useDebounceFn(() => {
  // const columns: T[][] = Array.from({ length: cols.value }, () => [])
  // const heights: number[] = Array.from({ length: cols.value }, (_, index) =>
  //   index < props.leftTopParams.columnNum ? carouselHeight.value : 0
  // )
  const datas = Array.from<ColumnsDataType, ColumnsDataType>(
    { length: cols.value },
    (_, index) => ({
      columnId: index,
      items: [],
      totalHeight: index < props.leftTopParams.columnNum ? carouselHeight.value : 0
    })
  )

  const defaultItemHeight = props.waterFallParams.defaultItemHeight

  for (const item of allItems.value) {
    const minHeightItem = minBy(datas, item => item.totalHeight)
    let minIndex = datas.findIndex(item => item === minHeightItem)

    // 将元素添加到该列并更新高度
    // console.log(111111111, columns[minIndex], minIndex, columns, heights, minHeight)
    if (datas[minIndex]) {
      datas[minIndex].items.push(item)
      let height =
        (colWidth.value * (item.height || defaultItemHeight)) / (item.width || colWidth.value)
      // let height = item.displayHeight || defaultItemHeight
      height += props.waterFallParams.gutter
      datas[minIndex].totalHeight += height
    }
  }
  // console.log('columns', datas)
  // requestAnimationFrame(() => {
  //   // console.log('requestAnimationFrame')
  //   columnData.value = datas
  // })
  columnData.value = datas
}, debounceTime)

watch(
  [cols, allItems],
  ([_, newAllItems], [__, noldAllItems]) => {
    if (JSON.stringify(newAllItems) === JSON.stringify(noldAllItems)) {
      // console.log('columnData.value', columnData.value, newAllItems, noldAllItems)
      debounceTime.value = 0
    } else {
      debounceTime.value = 300
    }
    getColumnsData()
  },
  { deep: true }
)

// watch(
//   allItems,
//   () => {
//     console.log('watch allItems', allItems.value)
//   },
//   { deep: true }
// )

// watch(cols, () => {
//   console.log('watch cols', cols.value)
// })

const containerTop = ref(0)

watch([containerRef, scrollContainer], () => {
  if (containerRef.value && scrollContainer.value) {
    containerTop.value =
      containerRef.value.getBoundingClientRect().top -
      scrollContainer.value.getBoundingClientRect().top
    // setTimeout(() => {
    //   scrollContainerOption.y.value = containerTop.value
    // }, 5000)
  }
})
</script>

<template>
  <div
    ref="containerRef"
    class="waterfall"
    :style="{
      padding: waterFallParams.hasAroundGutter ? `0 ${waterFallParams.gutter}px` : undefined,
      paddingTop: `${waterFallParams.gutter}px`
    }"
  >
    <!-- <div style="position: absolute; right: 0; background-color: black; z-index: 99">
      scrollContainerOption:{{ scrollContainerOption }}
      <br />
      visibleHeight:{{ scrollContainer?.clientHeight - containerTop }}
      <br />
      containerTop:{{ containerTop }}
      <br />
      colWidth:{{ colWidth }}
    </div> -->
    <div
      ref="waterfallWrapper"
      class="waterfall-wrapper"
      :style="{ gap: `${waterFallParams.gutter}px` }"
    >
      <div
        v-if="leftTopParams"
        ref="emptySpaceRef"
        class="item empty-space"
      >
        <div
          class="empty-container"
          :style="emptySapceStyle"
        >
          <slot
            name="empty-space"
            :style="emptySapceStyle"
          />
        </div>
      </div>
      <div
        v-for="(col, index) in columnData"
        :key="col.columnId"
        v-element-size="columnOnResize"
        class="column"
        :style="{
          gap: `${waterFallParams.gutter}px`
          // paddingTop:
          //   index < leftTopParams.columnNum
          //     ? `calc(${emptySapceStyle.height} + ${
          //         leftTopParams.widthHeightRatio ? waterFallParams.gutter : 0
          //       }px)`
          //     : undefined
        }"
      >
        <MyColumn
          v-if="scrollContainer?.clientHeight"
          :buffer="waterFallParams.buffer"
          :column-id="col.columnId"
          :default-item-height="waterFallParams.defaultItemHeight"
          :gutter="waterFallParams.gutter"
          :items="col.items"
          :key-field="keyField"
          :offset-top="
            index < leftTopParams.columnNum
              ? carouselHeight + (leftTopParams.columnNum ? waterFallParams.gutter : 0)
              : 0
          "
          :scroll-top="scrollContainerOption.y.value - containerTop"
          :show-dominant-color="showDominantColor"
          :visible-height="scrollContainer?.clientHeight - containerTop"
        >
          <!-- <template v-for="item in $slots">
            <slot :name="item?.name" />
          </template> -->
          <template #item="{ item, imgId }">
            <slot
              :img-id="imgId"
              :item="item"
              name="item"
            />
          </template>
        </MyColumn>
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.waterfall {
  .waterfall-wrapper {
    position: relative;
    display: flex;
    will-change: height;

    .empty-space {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 9;

      .empty-container {
        overflow: hidden;
        border-radius: 4px;
      }
    }

    .column {
      position: relative;
      flex: 1;

      img {
        object-fit: cover;
        width: 100%;
      }
    }
  }
}
</style>
